﻿/*
 * DotNetCrypt - an open source library of cryptographic algorithms for .NET
 * Copyright (C) 2009 David Musgrove
 * 
 * This file is part of DotNetCrypt.
 *
 * DotNetCrypt is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DotNetCrypt is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;

namespace DotNetCrypt
{
// ReSharper disable InconsistentNaming
    /// <summary>
    /// Performs a cryptographic transformation of data using the
    /// <see cref="IDEA" /> algorithm. This class cannot be inherited.
    /// </summary>
    public sealed class IDEAManagedTransform : ManagedTransformBase
// ReSharper restore InconsistentNaming
    {
        private const uint MULTIPLICATION_MODULUS = 0x10001;

        private uint[] _expandedKey;
        private uint[] _decryptionKey;

        internal IDEAManagedTransform(ISymmetricAlgorithm algorithm, byte[] rgbKey, byte[] rgbIv, TransformDirection transformDirection)
            : base(algorithm, rgbIv, transformDirection, Endianness.Big)
        {
            Mode = algorithm.ExtendedMode;
            ExpandKey(rgbKey);
        }

        internal bool IsWeakKey
        {
            get
            {
                return false;
            }
        }

        private void ExpandKey(byte[] key)
        {
            _expandedKey = new uint[52];
            int position = 0;

            for (int i = 0; i < 8; i++)
            {
                _expandedKey[i] = (uint)(key[position++]) << 8 |
                    key[position++];
            }

            for (int j = 1; j < 7; j++)
            {
                position = j << 3;
                for (int i = position; i < position + 6; i++)
                {
                    if (i == 52) break;
                    _expandedKey[i] = ((_expandedKey[i - 7] << 9) | (_expandedKey[i - 6] >> 7)) & 0xffff;
                }
                if (j < 6)
                {
                    _expandedKey[position + 6] = ((_expandedKey[position - 1] << 9) | (_expandedKey[position - 8] >> 7)) & 0xffff;
                    _expandedKey[position + 7] = ((_expandedKey[position - 8] << 9) | (_expandedKey[position - 7] >> 7)) & 0xffff;
                }
            }

            for (int i = 0; i < 52; i++)
            {
                if ((_expandedKey[i] & 0xffff) == 0) _expandedKey[i] = 0x10000;
            }

            _decryptionKey = new uint[52];
            int decryptionPosition = 54;
            uint temp = 0;

            for (int i = 0; i < 52; i++)
            {
                switch (i % 6)
                {
                    case 0:
                        decryptionPosition -= 6;
                        Inverse(_expandedKey[i], ref temp);
                        _decryptionKey[decryptionPosition++] = temp;
                        break;
                    case 1:
                        if ((i > 6) && (i < 49))
                            _decryptionKey[++decryptionPosition] = (0 - _expandedKey[i]) & 0xffff;
                        else
                            _decryptionKey[decryptionPosition++] = (0 - _expandedKey[i]) & 0xffff;
                        break;
                    case 2:
                        if ((i > 6) && (i < 49))
                            _decryptionKey[decryptionPosition++ - 1] = (0 - _expandedKey[i]) & 0xffff;
                        else
                            _decryptionKey[decryptionPosition++] = (0 - _expandedKey[i]) & 0xffff;
                        break;
                    case 3:
                        Inverse(_expandedKey[i], ref temp);
                        _decryptionKey[decryptionPosition++] = temp;
                        break;
                    case 4:
                        decryptionPosition -= 6;
                        _decryptionKey[decryptionPosition++] = _expandedKey[i];
                        break;
                    case 5:
                        _decryptionKey[decryptionPosition++] = _expandedKey[i];
                        break;
                }
            }
        }

        static private void Inverse(uint value, ref uint inverse)
        {
            if (value <= 1)
            {
                inverse = value;
                return;
            }

            inverse = 1;
            uint oldInverse = 0;
            uint dividend = MULTIPLICATION_MODULUS;
            while (true)
            {

                uint remainder = dividend % value;
                uint quotient = dividend / value;
                if (remainder == 0)
                {
                    if ((inverse & 0x1000000) != 0) inverse += MULTIPLICATION_MODULUS;
                    inverse &= 0xffff;
                    if (inverse == 0) inverse = 0x10000;
                    return;
                }

                dividend = value;
                value = remainder;
                uint temp = inverse;
                inverse = oldInverse - quotient * inverse;
                oldInverse = temp;
            }
        }

        static private void Multiply(ref uint value1, uint value2)
        {
            value1 &= 0xffff;
            value2 &= 0xffff;
            if (value1 == 0) value1 = 0x10000;
            if (value2 == 0) value2 = 0x10000;

            value1 *= value2;

            uint subtractor = value1 & 0xffff0000;
            if ((value1 >> 16) > (value1 & 0xffff)) subtractor -= 0x10000;
            subtractor += subtractor >> 16;

            value1 -= subtractor;
            value1 &= 0xffff;
        }

        /// <summary>
        /// Performs the encryption transformation on a block of bytes that
        /// have been translated into words using the big endian convention.
        /// </summary>
        /// <param name="plain">
        /// The words to encrypt.
        /// </param>
        [CLSCompliant(false)]
        protected internal override void Encrypt(uint[] plain)
        {
            var state = new uint[4];
            state[0] = plain[0] >> 16;
            state[1] = plain[0] & 0xffff;
            state[2] = plain[1] >> 16;
            state[3] = plain[1] & 0xffff;
            int keyPosition = 0;

            uint working1;
            for (int i = 0; i < 8; i++)
            {
                Multiply(ref state[0], _expandedKey[keyPosition++]);
                state[1] += _expandedKey[keyPosition++];
                state[1] &= 0xffff;
                state[2] += _expandedKey[keyPosition++];
                state[2] &= 0xffff;
                Multiply(ref state[3], _expandedKey[keyPosition++]);
                working1 = state[0] ^ state[2];
                uint working2 = state[1] ^ state[3];
                Multiply(ref working1, _expandedKey[keyPosition++]);
                working2 += working1;
                Multiply(ref working2, _expandedKey[keyPosition++]);
                working1 += working2;
                working1 &= 0xffff;
                state[0] ^= working2;
                state[2] ^= working2;
                state[1] ^= working1;
                state[3] ^= working1;
                working1 = state[2];
                state[2] = state[1];
                state[1] = working1;
            }

            working1 = state[2];
            state[2] = state[1];
            state[1] = working1;

            Multiply(ref state[0], _expandedKey[keyPosition++]);
            state[1] += _expandedKey[keyPosition++];
            state[1] &= 0xffff;
            state[2] += _expandedKey[keyPosition++];
            state[2] &= 0xffff;
            Multiply(ref state[3], _expandedKey[keyPosition]);

            plain[0] = (state[0] << 16) | (state[1] & 0xffff);
            plain[1] = (state[2] << 16) | (state[3] & 0xffff);
        }

        /// <summary>
        /// Performs the decryption transformation on a block of bytes that
        /// have been translated into words using the big endian convention.
        /// </summary>
        /// <param name="cipher">
        /// The words to decrypt.
        /// </param>
        [CLSCompliant(false)]
        protected internal override void Decrypt(uint[] cipher)
        {
            var state = new uint[4];
            state[0] = cipher[0] >> 16;
            state[1] = cipher[0] & 0xffff;
            state[2] = cipher[1] >> 16;
            state[3] = cipher[1] & 0xffff;
            int keyPosition = 0;

            uint working1;
            uint working2;
            for (int i = 0; i < 8; i++)
            {
                Multiply(ref state[0], _decryptionKey[keyPosition++]);
                state[1] += _decryptionKey[keyPosition++];
                state[1] &= 0xffff;
                state[2] += _decryptionKey[keyPosition++];
                state[2] &= 0xffff;
                Multiply(ref state[3], _decryptionKey[keyPosition++]);
                working1 = state[0] ^ state[2];
                working2 = state[1] ^ state[3];
                Multiply(ref working1, _decryptionKey[keyPosition++]);
                working2 += working1;
                working2 &= 0xffff;
                Multiply(ref working2, _decryptionKey[keyPosition++]);
                working1 += working2;
                working1 &= 0xffff;
                state[0] ^= working2;
                state[2] ^= working2;
                state[1] ^= working1;
                state[3] ^= working1;
                working1 = state[2];
                state[2] = state[1];
                state[1] = working1;
            }

            working1 = state[2];
            state[2] = state[1];
            state[1] = working1;

            Multiply(ref state[0], _decryptionKey[keyPosition++]);
            state[1] += _decryptionKey[keyPosition++];
            state[1] &= 0xffff;
            state[2] += _decryptionKey[keyPosition++];
            state[2] &= 0xffff;
            Multiply(ref state[3], _decryptionKey[keyPosition]);

            cipher[0] = (state[0] << 16) | (state[1] & 0xffff);
            cipher[1] = (state[2] << 16) | (state[3] & 0xffff);
        }

        /// <summary>
        /// Clears all potentially sensitive data stores.
        /// </summary>
        protected internal override void Reset()
        {
            if (_decryptionKey != null)
            {
                Array.Clear(_decryptionKey, 0, _decryptionKey.Length);
                _decryptionKey = null;
            }
            if (_expandedKey != null)
            {
                Array.Clear(_expandedKey, 0, _expandedKey.Length);
                _expandedKey = null;
            }
            base.Reset();
        }
    }
}